from collections import defaultdict
import math
import time
import json
from loguru import logger
import commons
from orders import OrdersHelper
from configs import Configs
from data_helper import DataHelper


class Strategies:
    interval2seconds = {"1m": 60, "3m": 180, "5m": 300, "15m": 900, "30m": 1800, "1h": 3600, "2h": 7200, "4h": 14400,
                        "1d": 86400}
    diff_rate_slope45 = 0.024  # busd/second

    def __init__(self):
        self.strategies = None
        self.data_helper = None
        self.k_lines = None
        self.orders_helper = None
        self.orders_db = None
        self.orders_bi = None
        self.operated_strategies_by_s8 = dict()  # key为sid，value为被操作时的蜡烛时间
        self.strategy_5_current_p = None
        self.strategy_5_current_time = None
        self.auto_params = None
        self.cache_strategy_105 = dict()
        self.cache = defaultdict(dict)

    @classmethod
    def rate2angle(cls, rate):
        return math.atan(rate / cls.diff_rate_slope45) / math.pi * 180

    def get_strategies_from_db(self):
        db = commons.get_db()
        cursor = db.cursor()
        cursor.execute("SELECT id, parameters, category, name FROM strategies WHERE on_off=1;")
        self.strategies = {row[0]: list(row) for row in cursor.fetchall()}
        db.close()
        return self.strategies

    def get_autos_from_db(self):
        db = commons.get_db()
        cursor = db.cursor()
        cursor.execute("SELECT parameters FROM auto_params WHERE on_off=1;")
        self.auto_params = [row[0] for row in cursor.fetchall()]
        db.close()
        return self.auto_params

    @staticmethod
    def get_relations_from_db(sid):
        db = commons.get_db()
        cursor = db.cursor()
        cursor.execute(f"SELECT sid1 FROM relations WHERE sid0='{sid}'")
        ret = set([row[0] for row in cursor.fetchall()])
        db.close()
        return ret

    def get_strategies(self):
        return self.strategies

    @staticmethod
    def set_strategy_on_off(sid, on_off):
        db = commons.get_db()
        cursor = db.cursor()
        sql = f"UPDATE strategies SET on_off={on_off} WHERE id={sid}"
        ret = cursor.execute(sql)
        db.commit()
        db.close()
        return ret

    def get_not_closed_orders_by_strategies(self, strategies):
        """
        返回关联策略的未闭环买单。未闭环买单指已经成交、但未有对应卖单的买单。
        :param strategies: 关联策略的集合
        """
        strategies = set(strategies)

        orders_db = self.orders_db
        orders_bi = self.orders_bi

        orders_buy_closed = set([orders_db[order_id][2] for order_id in orders_db])  # 被关联的订单号集合，这些单都闭环了
        orders_buy_not_closed = [order_id for order_id in orders_db if
                                 str(order_id) not in orders_buy_closed and  # 未闭环
                                 int(orders_db[order_id][3]) in strategies and  # 属于关联策略的订单
                                 order_id in orders_bi and  # 能在币安找到对应单的
                                 orders_bi[order_id]["side"] == "BUY" and  # 是买单
                                 orders_bi[order_id]["status"] == "FILLED"]  # 已成交

        return orders_buy_not_closed

    def get_sell_orders_by_strategies(self, strategies):
        strategies = set(strategies)

        orders_db = self.orders_db
        orders_bi = self.orders_bi
        orders_sell = [order_id for order_id in orders_db if
                       int(orders_db[order_id][3]) in strategies and
                       order_id in orders_bi and  # 能在币安找到对应单的
                       orders_bi[order_id]["side"] == "SELL" and  # 是卖单
                       orders_bi[order_id]["status"] == "FILLED"]  # 已成交
        return list(set(orders_sell))

    def get_orders_by_strategies(self, strategies):
        strategies = set(strategies)
        orders_db = self.orders_db
        orders = [order_id for order_id in orders_db if
                  int(orders_db[order_id][3]) in strategies]
        return list(set(orders))

    def is_strategy_over_operating(self, strategy_id, limit_times, candle):
        """
        检查策略在周期内是否下单超过限制
        :param strategy_id:
        :param limit_times:
        :param candle:
        """
        orders = [order_id for order_id in self.orders_db if
                  order_id in self.orders_bi and
                  self.orders_db[order_id][3] == strategy_id and self.orders_bi[order_id]['time'] >= candle[0]]
        logger.debug(f"指定蜡烛周期内，策略{strategy_id}已下单为：{orders}")
        return len(orders) >= limit_times

    def is_strategies_over_operating(self, strategy_ids, limit_times, candle):
        """
        检查策略集合在周期内是否下单超过限制
        :param strategy_ids:
        :param limit_times:
        :param candle:
        """
        orders = [order_id for order_id in self.orders_db if
                  order_id in self.orders_bi and
                  self.orders_db[order_id][3] in strategy_ids and self.orders_bi[order_id]['time'] >= candle[0]]
        logger.debug(f"指定蜡烛周期内，策略集合{strategy_ids}已下单为：{orders}")
        return len(orders) >= limit_times

    def _process_strategy_1(self, strategy_id=1):
        """
        策略1 - EMA趋势买入策略
        使用数据：
        - 程序K线数据 (self.k_lines)
        - EMA指标数据 (param["ema列表"])
        - 策略参数：x0, x1, x2, k1, k2, n, 不交易时间段, 每小时开始生效时刻, 本条蜡烛未发生过卖出操作才买入, 在指定ema下不买入
        - 订单数据 (self.orders_db, self.orders_bi)
        - 配置数据 (Configs)
        """
        pass

    def get_bi_order_price(self, orders_bi, order_id):
        order_price = float(orders_bi[order_id]["price"])
        cummulative_quote_qty = float(orders_bi[order_id]["cummulativeQuoteQty"])
        executed_qty = float(orders_bi[order_id]["executedQty"])
        if order_price < 1e-5 or (cummulative_quote_qty > 1e-5 and executed_qty > 1e-5):
            order_price = cummulative_quote_qty / executed_qty
        return order_price

    def _process_strategy_3(self, strategy_id=3):
        """
        策略3 - 关联策略利润卖出策略
        使用数据：
        - 程序K线数据 (self.k_lines)
        - 关联策略订单数据 (relations)
        - 策略参数：x0, x1
        - 订单数据 (orders_db, orders_bi)
        """
        pass

    def _process_strategy_5(self, strategy_id=5):
        """
        策略5 - 限价买入策略
        使用数据：
        - 程序K线数据 (self.k_lines)
        - 计算A所用K线数据 (interval_a)
        - EMA指标数据 (param["ema列表"])
        - 策略参数：计算A所用k线, x, 条件1on_off, 条件2on_off, 撤单on_off, 不交易时间段
        - 订单数据 (orders_db, orders_bi)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_6(self, strategy_id=6):
        """
        策略6 - 限价卖单策略
        使用数据：
        - 关联策略订单数据 (relations)
        - 策略参数：x
        - 订单数据 (self.orders_db, self.orders_bi)
        """
        pass

    def _process_strategy_7(self, strategy_id=7):
        """
        策略7 - EMA角度关机策略
        使用数据：
        - 指定K线数据 (param["k线"])
        - EMA指标数据 (param["ema列表"])
        - 策略参数：k线, ema列表, n
        - 关联策略数据 (relations)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_8(self, strategy_id=8):
        """
        策略8 - EMA递减关机策略
        使用数据：
        - 指定K线数据 (param["k线"])
        - EMA指标数据 (param["ema列表"])
        - 策略参数：k线, ema列表
        - 关联策略数据 (relations)
        - 配置数据 (Configs)
        - 操作记录 (self.operated_strategies_by_s8)
        """
        pass

    def _process_strategy_9(self, strategy_id=9):
        """
        策略9 - 振幅涨幅关机策略
        使用数据：
        - 指定K线数据 (param["k线"])
        - 策略参数：k线, 振幅范围, 涨幅指定值
        - 关联策略数据 (relations)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_10(self, strategy_id=10):
        """
        策略10 - EMA递减关机策略
        使用数据：
        - 指定K线数据 (param["k线"])
        - EMA指标数据 (param["ema列表"])
        - 策略参数：k线, ema列表
        - 关联策略数据 (relations)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_11(self, strategy_id=11):
        """
        策略11 - EMA角度关机策略
        使用数据：
        - 指定K线数据 (param["k线"])
        - EMA指标数据 (param["ema列表"])
        - 策略参数：k线, ema列表, x, y
        - 关联策略数据 (relations)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_12(self, strategy_id=12):
        """
        策略12 - EMA角度开机策略
        使用数据：
        - 指定K线数据 (param["k线"])
        - EMA指标数据 (param["ema列表"])
        - 策略参数：k线, ema列表, ema角度范围
        - 关联策略数据 (relations)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_13(self, strategy_id=13):
        """
        策略13 - EMA递增开机策略
        使用数据：
        - 指定K线数据 (param["k线"])
        - EMA指标数据 (param["ema列表"])
        - 策略参数：k线, ema列表
        - 关联策略数据 (relations)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_14(self, strategy_id=14):
        """
        策略14 - 振幅涨幅开机策略
        使用数据：
        - 指定K线数据 (param["k线"])
        - 策略参数：k线, 振幅范围, 涨幅指定值
        - 关联策略数据 (relations)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_15(self, strategy_id=15):
        """
        策略15 - 复杂限价卖单策略
        使用数据：
        - 程序K线数据 (self.k_lines)
        - 关联策略订单数据 (relations)
        - 策略参数：Q
        - 订单数据 (self.orders_db, self.orders_bi)
        """
        pass

    def _process_strategy_16(self, strategy_id=16):
        """
        策略16 - 动态参数调整策略
        使用数据：
        - 指定K线数据 (param["k线"])
        - 策略参数：k线, Q
        - 策略5参数数据
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_18(self, strategy_id=18):
        """
        策略18 - 复合条件开机策略
        使用数据：
        - 指定K线数据 (param["k线"], param["k线a"])
        - EMA指标数据 (param["ema列表"])
        - 策略参数：k线, k线a, ema列表, ema角度范围, 振幅范围a, 涨幅指定值a
        - 关联策略数据 (relations)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_19(self, strategy_id=19):
        """
        策略19 - 双条件关机策略
        使用数据：
        - 指定K线数据 (param["k线"], param["k线a"])
        - 策略参数：k线, k线a, 振幅范围, 振幅范围a, 涨幅指定值, 涨幅指定值a
        - 关联策略数据 (relations)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_20(self, strategy_id=20):
        """
        策略20 - 动态买入金额调整策略
        使用数据：
        - 指定K线数据 (param["k线"])
        - 策略参数：k线, 范围及金额
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_21(self, strategy_id=21):
        """
        策略21 - 时间条件卖出策略
        使用数据：
        - 程序K线数据 (self.k_lines)
        - 指定K线数据 (param["k线"])
        - 关联策略订单数据 (relations)
        - 策略参数：k线, x0, x1, 时间范围及xi
        - 订单数据 (orders_db, orders_bi)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_35(self, strategy_id=35):
        """
        策略35 - 自动参数调整策略
        使用数据：
        - 自动参数数据 (self.auto_params)
        - 策略参数：k线, 策略id, 参数, 时间范围及值
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_36(self, strategy_id=36):
        """
        策略36 - 时间范围关机策略
        使用数据：
        - 策略参数：k线, 指定的时间范围
        - 关联策略数据 (relations)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_37(self, strategy_id=37):
        """
        策略37 - 策略交易次数限制策略
        使用数据：
        - 指定K线数据 (param['k线,策略,次数'])
        - 策略参数：k线,策略,次数
        - 订单数据 (self.orders_db, self.orders_bi)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_94(self, strategy_id=94):
        """
        策略94 - 策略集合交易次数限制策略
        使用数据：
        - 指定K线数据 (param['k线,策略集合,次数'])
        - 策略参数：k线,策略集合,次数
        - 订单数据 (self.orders_db, self.orders_bi)
        - 配置数据 (Configs)
        """
        pass

    def _process_strategy_105(self, strategy_id=105):
        """
        策略105 - 复杂限价买入策略
        使用数据：
        - 程序K线数据 (self.k_lines)
        - 计算A所用K线数据 (param["计算A所用k线"])
        - 条件7指定K线数据 (param["条件7指定k线"])
        - EMA指标数据 (param["ema列表"])
        - 策略参数：计算A所用k线, 条件7指定k线, ema列表, x, Q, 条件0on_off, 条件1on_off, 条件2on_off, 条件4on_off, 条件7on_off, 马上撤单on_off, 撤单on_off, n, 不交易时间段
        - 订单数据 (orders_db, orders_bi)
        - 配置数据 (Configs)
        - 缓存数据 (self.cache_strategy_105)
        """
        pass

    def _process_strategy_114(self, strategy_id=114):
        """
        策略114 - 智能卖出策略
        使用数据：
        - 程序K线数据 (self.k_lines)
        - 指定K线数据 (param["指定k线"])
        - EMA指标数据 (param["ema列表"])
        - 关联策略订单数据 (relations)
        - 策略参数：指定k线, ema列表, N, Q, h, Y, k、X、E列表, G, 生效时间段, 条件1on_off
        - 订单数据 (self.orders_db, self.orders_bi)
        - 配置数据 (Configs)
        - 缓存数据 (self.cache)
        """
        pass

    def _process_strategy_115(self, strategy_id=115):
        """
        策略115 - 策略绩效统计策略
        使用数据：
        - 程序K线数据 (self.k_lines)
        - 策略参数：分组, 统计时长
        - 订单数据 (self.orders_db, self.orders_bi)
        """
        pass

    def _process_strategy_200(self, strategy_id=200):
        """
        策略200 - 强制卖出策略
        使用数据：
        - 无特殊数据需求
        """
        pass

    def _process_strategy_201(self, strategy_id=201):
        """
        策略201 - 动态参数调整策略
        使用数据：
        - 程序K线数据 (self.k_lines)
        - 关联策略数据 (param["关联策略"])
        - 策略参数：关联策略, xz列表, u
        - 缓存数据 (self.cache)
        """
        pass

    def process(self):
        self.get_strategies_from_db()
        self.get_autos_from_db()
        self.data_helper = DataHelper()
        self.k_lines = self.data_helper.get_k_lines()
        self.orders_helper = OrdersHelper()
        self.orders_bi = self.orders_helper.get_orders_binance()
        self.orders_db = self.orders_helper.get_orders_db()

        strategies_dict = {
            1: self._process_strategy_1,
            3: self._process_strategy_3,
            5: self._process_strategy_5,
            6: self._process_strategy_6,
            7: self._process_strategy_7,
            8: self._process_strategy_8,
            9: self._process_strategy_9,
            10: self._process_strategy_10,
            11: self._process_strategy_11,
            12: self._process_strategy_12,
            13: self._process_strategy_13,
            14: self._process_strategy_14,
            15: self._process_strategy_15,
            16: self._process_strategy_16,
            18: self._process_strategy_18,
            19: self._process_strategy_19,
            20: self._process_strategy_20,
            21: self._process_strategy_21,
            35: self._process_strategy_35,
            36: self._process_strategy_36,
            37: self._process_strategy_37,
            94: self._process_strategy_94,
            105: self._process_strategy_105,
            114: self._process_strategy_114,
            115: self._process_strategy_115,
            200: self._process_strategy_200,
            201: self._process_strategy_201,
        }

        def filter_strategies_and_run(category):
            """
            按策略种类分批执行策略
            :param category: 策略种类，包括开关机、自动化、买卖等
            :return:
            """
            for sid, row in self.strategies.items():
                if row[2] == category:
                    if sid in strategies_dict:
                        strategies_dict[sid]()
                    else:
                        name = row[3]
                        clone_sid = int(name[2:name.find('的')])
                        try:
                            strategies_dict[clone_sid](strategy_id=sid)
                        except KeyError as err:
                            logger.error(err)

        # 开机策略
        filter_strategies_and_run(category="开机")
        self.get_strategies_from_db()

        # 关机策略
        filter_strategies_and_run(category="关机")
        self.get_strategies_from_db()

        # 自动化策略
        filter_strategies_and_run(category="自动化")

        # 买卖策略
        filter_strategies_and_run(category="买")
        filter_strategies_and_run(category="卖")


def test():
    strategies = Strategies()
    logger.debug(strategies.get_strategies())
    logger.debug(strategies.get_strategies_from_db())
    logger.debug(strategies.get_strategies())


if __name__ == '__main__':
    test()
